#pragma once

#include <iostream>
#include <vector>
#include <semaphore.h>

using namespace std;

const int g_cap = 5;

// 信号量的申请类似于预定机制，如果能够成功申请到，那么肯定有真正属于自己的资源
// 所以对于多生产者，多消费者的场景，可以先申请信号量，再进行对锁的申请
// 先让这些线程获取到属于自己的资源，然后再进行串行生产和消费

template <class T>
class RingQueue
{
public:
    RingQueue(int cap = g_cap)
        : ring_(cap)
        , pdcIndex_(0)
        , cstmIndex_(0)
    {
        // 刚开始  空间信号量 roomsem 可以申请的是 size() 个
        sem_init(&roomsem_, 0, ring_.size());
        // 而      数据信号量 datasem 可以申请的是 0 个
        sem_init(&datasem_, 0, 0);
        pthread_mutex_init(&pdcMutex_);
        pthread_mutex_init(&cstmMutex_);
    }
    // 生产
    void push(const T& val)
    {
        sem_wait(&roomsem_); // 生产需要空间
        pthread_mutex_lock(&pdcMutex_);

        // 申请指定信号量，成功就让相应的信号量 --
        // pdcIndex 是有生产者会访问，所以不算是临界资源
        ring_[pdcIndex_] = val; // 生产

        pdcIndex_ ++;
        pdcIndex_ %= ring_.size(); // 更新下标，防止越界

        pthread_mutex_unlock(&pdcMutex_);
        sem_post(&datasem_);
    }
    // 消费
    T pop()
    {
        sem_wait(&datasem_);
        pthread_mutex_lock(&cstmMutex_);

        // 申请消费数据，成功那么 数据信号量 --
        T src = ring_[cstmIndex_];
        // 消费成功，空间多了一个，空间信号量 ++

        cstmIndex_ ++;
        cstmIndex_ %= ring_.size();

        pthread_mutex_unlock(&cstmMutex_);
        sem_post(&roomsem_);
        
        return src;
    }

    ~RingQueue()
    {
        sem_destroy(&roomsem_);
        sem_destroy(&datasem_);

        pthread_mutex_destroy(&pdcMutex_);
        pthread_mutex_destroy(&cstmMutex_);
    }
private:
    vector<T> ring_; // 环形队列
    sem_t roomsem_; // 空间计数器，producer
    sem_t datasem_; // 数据结束期，customer
    uint32_t pdcIndex_; // 当前生产者读取到的位置 produce Index
    uint32_t cstmIndex_; // 当前消费者读取到的位置 custom Index

    // 如果是多生产者和多消费者，那么只需要为这些“角色”上锁即可
    // 反正只允许一个线程生产和一个线程消费
    pthread_mutex_t pdcMutex_;
    pthread_mutex_t cstmMutex_;
};